#ifndef OHRenderNode_DEFINED
#define OHRenderNode_DEFINED

#include "utils/SkClipShape.h"
#include "core/SkCanvas.h"
#include "core/SkPictureRecorder.h"
#include <js_native_api_types.h>
#include <list>
#include <unordered_map>
#include <uv.h>

typedef void (*NodeDrawCallback)(void *context, void *canvas);

class SkPicture;
class SkPictureRecorder;

#define NODE_SIZE_ALIGNMENT 1024
#define FLOAT_IS_EQUAL(a, b, eps) (abs((a) - (b)) < eps)

class NodeStatusModify {
public:
    enum NodeOp {
        INVALIDATE = 0,
        REMOVE_CHILD,
        INSERT_CHILD_AFTER,
        CLEAR_CHILDREN,
        SIZE,
        POSITION,
        TRANSFORM,
        SHAPE_CLIP,
        SHAPE_CLIP_RRECT,
        SHAPE_CLIP_PATH,
        APPEND_CHILD,
        MARK_GROUP,
        MARK_NO_GROUP
    };
    
    static void BuildInstance(napi_env env, napi_value fun);
    
    static NodeStatusModify *GetInstance() {
        static NodeStatusModify fInstance;
        return &fInstance;
    };

    void doModify();
    void pushTo(NodeOp op, napi_value node1, napi_value node2, napi_value node3);
    void pushTo(NodeOp op, napi_value node, const SkVector &vector);
    void pushTo(NodeOp op, napi_value node, const SkRect &rect);
    void pushTo(NodeOp op, napi_value node, const SkMatrix &matrix);
    void pushTo(NodeOp op, napi_value node, const SkRRect &rrect);
    void pushTo(NodeOp op, napi_value node, const SkPath &path);
private:
    napi_ref fJsObject = nullptr;
    std::vector<int> fOps;
    std::vector<float> fNumbers;
    std::vector<napi_value> fNodes;
    std::vector<SkString> fStrs;
};

class OHRenderNode {
public:
    OHRenderNode();
    OHRenderNode(napi_value jsNode);
    ~OHRenderNode();
    
    void dispose();

    void registerCallback(void *context, NodeDrawCallback callback);
    
    // RenderNode status update
    void updateNodeStatus();
    void updateRelativeLocation();
    void updateRelativeClipArea();
    void updateRelativeMatrix();
    void updateDrawingOffset();
    void updateNowFrame();
    
    // set before all drawing command
    void setFixedFrame(SkRect &fixedFrame);
    
    // SkPictureRecord finish status update
    void setRealFrame(SkRect &realFrame, bool noLimit = true);

    void updateFatherNodeFrame(SkRect &fatherFrame);
    
    // SkPicture playback status update
    bool updateFatherMatrix(SkMatrix &fatherMatrix);
    void updateDeviceClip(SkClipShape &clip);
    void updateClipArea(SkClipShape &clip);
    void unsetClipArea();
    void updateClipAreaWithFrameChange();
    void updatePendingDiffShape(const SkClipShape &clip);
    
    // send status change to arkts
    void pushStatusToModify();
    void doRedraw();
    
    
    // use for no size limit skpicture
    SkRect getSuitFrameSize();
    
    SkRect getJSNodeSize();
    SkRect getFrameSize();
    
    // children operation
    void clearChildren();
    void restartChildAdd();
    void finishChildAdd();
    void appendChild(std::shared_ptr<OHRenderNode> childNode);
    void removeChild(OHRenderNode *childNode);
    void setParent(OHRenderNode *node);
    OHRenderNode *getParent();
    std::shared_ptr<SkPictureRecorder> getOrCreateNextSubRecorder();
    int getPaintChildrenNum() { return fChildOrderList.size(); }
    int getAllNodeChildrenNum() { return fChildList.size(); }
    void addPictureChildrenNum() { fPictureChildNum++; }
    int getPictureChildrenNum() { return fPictureChildNum; }
    std::optional<SkRect> getPaintChildArea() { return fChildPaintArea; }
    void addRecordCnt() { fRecordCnt++; }
    int getRecordCnt() { return fRecordCnt; }
    int updateDeltaChangedCnt(int fatherRecordCnt) {
        int delta = fatherRecordCnt - fRecordCnt;
        if (delta != fFatherRecordCntDelta && fFatherRecordCntDelta != 0) {
            fDeltaChangedCnt++;
        }
        fFatherRecordCntDelta = delta;
        return fDeltaChangedCnt;
    }
    
    void moveToInvisibleArea();
    
    void markActive(bool active) { fActive = active; }
    bool isActive() { return fActive; }
    bool isVisible() { return fVisible; }
    bool needDelayChildAppend() { return fNeedDelayChildAppend; }
    
    SkMatrix getMatrix() { return fFatherMatrix; }
    
//    SkRect getDrawArea();
    void setPaintArea(std::optional<SkRect> area) { fNowPaintArea = area; }
    std::optional<SkRect> getPaintArea() { return fNowPaintArea; }
    
    void nodeDraw(OH_Drawing_Canvas *canvas);
    
    uint64_t pictureDraw(OH_Drawing_Canvas *canvas, bool needCache = false);
    
    void updatePicture(SkPicture *picture);
    SkPicture *getPicture();
    
    std::shared_ptr<OHRenderNode> makeClone();
    
    float getDrawCost();
    
    SkRect getMaxDeviceFrame() { return fMaxDeviceFrame; }
    
    void markForbidRasterCache(bool disable = true) {
        fForbidRasterCache = disable;
    }

    void invalidate() {
        fMarkInvalidate = true;
    }

    bool isForbiddenRasterCache() {
        return fForbidRasterCache;
    }
    
    bool isInNode() { return !fDrawInPicture; }
    
    static std::shared_ptr<OHRenderNode> CreateNormalNode();
    
    static std::shared_ptr<OHRenderNode> CreateExternalNode(napi_value node);
    
    static void RenderNodeDraw(napi_env env, napi_value jsCanvas, napi_value jsNode);
    static void RenderNodeNotifyRedraw(napi_env env, napi_value jsNode);
    static void RenderNodeDoRedraw(uv_async_t *handle);
    
    static void SetNodeConstructor(napi_env env, napi_value fun);
    static void SetNodeStatusModifyConstructor(napi_env env, napi_value fun);
    static napi_value CreateRootRenderNode(void *context, NodeDrawCallback callback);
    static void SetPixelRatio(double ratio);
    
    static bool gEnableRasterCache;
    
    const int fNodeId = GenerateNextId();
private:
    napi_value setLiveWithJs();
    
    napi_value getLocalJsObject();
    
    napi_status invokeJsFunction(const char *name, size_t argc, const napi_value* argv, napi_value *result, napi_value node = nullptr);
    
    void updateScaleRatio(float fatherScaleRatio);
    
    void rasterCacheStatusUpdate();
    
    void generatePictureTreeCmdCache();
    
    static napi_value CreateJsNode();
    static napi_value CreateJsLiveNode(OHRenderNode **node);
    static void Destructor(napi_env env, void* nativeObject, void* finalize_hint);
//    static void NoneDestructor(napi_env env, void *nativeObject, [[maybe_unused]] void *finalize_hint) {
//    };
    static napi_ref gNodeConstructorRef;
    static double gVP2PXRatio;
    
    static std::atomic<int> gNodeNextId;
    static int GenerateNextId() {
        return gNodeNextId.fetch_add(1, std::memory_order_relaxed);
    }
    
    float fNodeDrawCost = 0;
    float fPictureDrawCost = 0;
    bool fForbidRasterCache = false;
    bool fIsInRasterCache = false;
    bool fReadyToCreateRasterCache = false;
    bool fRasterCacheStateHasChange = false;
    int fCacheReadyFrameCnt = 0;
    float fScaleRatio = 1.0f;
    float fFatherScaleRatio = 1.0f;
    
    size_t fNodeRedrawCnt = 0;
    
    // used for calc cost
    SkRect fMaxDeviceFrame;
    
    // native node status
    bool fFatherMatrixHasChanged = false;
    bool fClipShapeHasChanged = false;
    bool fNowFrameHasChanged = false;
    bool fRealFrameHasChanged = false;
    bool fFatherFrameHasChanged = false;
    bool fContentHasChanged = false;
    int fContentUnchangedFrame = 0;
    
    // arkts node status
    bool fSizeNeedUpdate = false;
    bool fPositionNeedUpdate = false;
    bool fClipNeedUpdate = false;
    bool fMatrixNeedUpdate = false;
    bool fNodeNeedRedraw = false;
    
    // "true" means node should be appended when drawPicture.
    // (means this picture should be draw as node)
    bool fActive = false;
    
    // limit one appended child every frame.
    bool fNeedDelayChildAppend = true;
    
    // clip info
    bool fHasClip = false;
    bool fCanUseClipAsFrame = true;
    bool fNeedClip = false;
    SkClipShape fNowClipShape;
    SkClipShape fPendingDiffShape;
    SkClipShape fDeviceClipShape;
    SkRect fNowClipRect;
    
    // node info
    bool fNoLimitSize = true;
    bool fVisible = false;
    bool fSizeFixed = false;
    bool fDrawInPicture = false;
    bool fNeedMoveToInvisibleArea = false;
    SkRect fNowFrame;
    SkRect fRealFrame;
    // nullopt means paint area is no limit.
    std::optional<SkRect> fNowPaintArea;
    SkMatrix fFatherMatrix;
    SkMatrix fFatherMatrixInv;
    SkRect fFatherFrame;
    
    // use to determine if the node should be drawn separately:
    // recordCount < canvas redraw cnt => content unchanged sometimes => should draw in separate node
    int fDeltaChangedCnt = 0;
    int fRecordCnt = 0;
    int fFatherRecordCntDelta = 0;
    
    // arkts RenderNode info
    SkSize fNowFrameSize = {0, 0};
    SkPoint fRelativeOffset = {0, 0};
    SkMatrix fMatrix;
    SkClipShape fRelativeClipShape;
    
    // drawing content
    bool fMarkInvalidate = false;
    float fOffsetX = 0.0;
    float fOffsetY = 0.0;
    SkPicture *fNodePicture = nullptr;
    uint64_t fNodeContentHash = 0;
    uint64_t fPictureTreeHash = 0;
    OHDrawingRecordCmdSPtr fPictureCmd = nullptr;
    OHDrawingRecordCmdSPtr fPictureTreeCmdCache = nullptr;
    
    // arkts node ref
    napi_ref fJsObject;
    bool fHasRef = false;
    bool fStatusOK = false;
    
    uv_async_t *fAsyncTask = nullptr;
    
    // used for root node
    sk_sp<SkPicture> fRecorderPicture;
    SkPictureRecorder *fRecorder = nullptr;
    void *fCallbackContext = nullptr;
    NodeDrawCallback fCallback = nullptr;
    
    int fPictureChildNum = 0;
    
    // children info
    bool fNeedReorder = false;
    // nullopt means there are no area limit child node--all other node must be append after. 
    std::optional<SkRect> fChildPaintArea = SkRect::MakeEmpty();
    std::list<std::shared_ptr<OHRenderNode>> fChildList;
    std::list<std::list<std::shared_ptr<OHRenderNode>>::iterator> fChildOrderList;
    std::list<std::list<std::shared_ptr<OHRenderNode>>::iterator> fChildAppend;
//    std::list<std::shared_ptr<OHRenderNode>>::iterator fStartChildIt = fChildList.end();
    std::unordered_map<OHRenderNode*, std::list<std::shared_ptr<OHRenderNode>>::iterator> fChildMap;
    OHRenderNode *fParent = nullptr;
    
    // sub picture recorder pool
    std::list<std::shared_ptr<SkPictureRecorder>> fSubRecorders;
    std::list<std::shared_ptr<SkPictureRecorder>>::iterator fSubRecorderNextIt = fSubRecorders.end();
    
};


#endif

